﻿/********************************************************************************

** 作者： XmPlatform

** 描述：XmHttpServer

*********************************************************************************/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Reflection;
using XmHttpServer.Rest;
using System.Collections.Specialized;
using System.IO;
using System.Windows.Forms;

namespace XmHttpServer
{
    /// <summary>
    /// HttpServer 主类
    /// </summary>
    public class HttpServer : IDisposable
    {
        //private static readonly ILog Log = LogManager.GetLogger(typeof(HttpServer));
        #region Logging

        private ConsoleWriter log = null;

 

        /// <summary>
        /// Gets or sets the <see cref="TextWriter"/> to which to write logs.
        /// </summary>
        public ConsoleWriter Log
        {
            get
            {
                if (log == null)
                {
                    log = new ConsoleWriter();
                }
                return log;
            }
            set
            {
                log = value;
            }
        }

        #endregion


        IDictionary<string, KeyValuePair<Type, Dictionary<string, System.Reflection.MethodInfo>>> typeReference;



        private bool _disposed;
        private TcpListener _listener;
        private readonly object _syncLock = new object();
        private readonly Dictionary<HttpClient, bool> _clients = new Dictionary<HttpClient, bool>();
        private HttpServerState _state = HttpServerState.Stopped;
        private AutoResetEvent _clientsChangedEvent = new AutoResetEvent(false);

        public HttpServerState State
        {
            get { return _state; }
            private set
            {
                if (_state != value)
                {
                    _state = value;

                    OnStateChanged(EventArgs.Empty);
                }
            }
        }

        public event HttpRequestEventHandler RequestReceived;
        public event ClientEventHandler ClientConnected;
        public event ClientEventHandler ClientDisconnected;

        protected virtual void OnRequestReceived(HttpRequestEventArgs e)
        {
            var ev = RequestReceived;

            if (ev != null)
                ev(this, e);
        }

        public event HttpExceptionEventHandler UnhandledException;

        protected virtual void OnUnhandledException(HttpExceptionEventArgs e)
        {
            var ev = UnhandledException;

            if (ev != null)
                ev(this, e);
        }

        public event EventHandler StateChanged;

        protected virtual void OnStateChanged(EventArgs e)
        {
            var ev = StateChanged;

            if (ev != null)
                ev(this, e);
        }

        public IPEndPoint EndPoint { get; set; }

        public int ReadBufferSize { get; set; }

        public int WriteBufferSize { get; set; }

        public string ServerBanner { get; set; }

        public TimeSpan ShutdownTimeout { get; set; }

        internal HttpServerUtility ServerUtility { get; private set; }

        private Assembly assembly = null;
        public Assembly Assembly {
            get
            {
                return assembly;
            }
            set
            {
                assembly = value;
                SetupReference();
            }
        }

        public IAuthenticator Authenticator { get; set; }

        public bool RequireAuthentication { get; set; }

        public string AuthenticateRealm { get; set; }

        private NameValueCollection directorys = new NameValueCollection();

        /// <summary>
        /// 提供文件服务的文件夹
        /// </summary>
        public NameValueCollection Directorys
        {
            get {
                return directorys;
            }
        }
        /// <summary>
        /// 添加供文件下载的目录
        /// </summary>
        /// <param name="absolutePath">对应提供服务的url路径</param>
        /// <param name="filePath">文件目录</param>
        public void addDirectory(string absoluteUrl, string filePath)
        {
            if (Directory.Exists(filePath))
            {
                directorys.Add(absoluteUrl, filePath);
            }
        }

        public HttpServer()
        {
            EndPoint = new IPEndPoint(IPAddress.Loopback, 0);

            ReadBufferSize = 4096;
            WriteBufferSize = 4096;
            ShutdownTimeout = TimeSpan.FromSeconds(30);

            ServerBanner = String.Format("XmServer/{0}", GetType().Assembly.GetName().Version);

            typeReference = new Dictionary<string, KeyValuePair<Type, Dictionary<string, System.Reflection.MethodInfo>>>();

            try
            {
                Authenticator = new BasicAuthenticator();
            }
            catch (NotImplementedException)
            {
                //TODO: make an even simpler authenticator for .net implementations without md5
            }
            catch (MemberAccessException)
            {
            }
            
        }
        /// <summary>
        /// 建立方法与参照的对应关系 typeReference
        /// </summary>
        private void SetupReference()
        {
            //Assembly ab = Assembly.GetExecutingAssembly();//获取当前运行的程序集
            if (assembly == null) return;
            Type[] typeArr = assembly.GetTypes(); //获取所有类
            foreach (Type type in typeArr)
            {
                if (typeof(IRestService).IsAssignableFrom(type))
                {
                    //获取所有“RestMethods”
                    Dictionary<string, System.Reflection.MethodInfo> supportedMethods = new Dictionary<string, System.Reflection.MethodInfo>();
                    foreach (MethodInfo mi in type.GetMethods())
                    {
                        if (mi.GetCustomAttributes(typeof(RestMethod), false).Length > 0)
                        {
                            //
                            supportedMethods[mi.Name] = mi;
                        }
                    }
                    typeReference.Add(type.Name/*.ToLower()*/, new KeyValuePair<Type, Dictionary<string, System.Reflection.MethodInfo>>(type, supportedMethods));
                }
            }
        }
        /// <summary>
        /// 获取类名的参照
        /// </summary>
        /// <param name="className"></param>
        /// <param name="throwOnError"></param>
        /// <returns></returns>
        private KeyValuePair<Type, Dictionary<string, System.Reflection.MethodInfo>> GetTypeReference(string className, bool throwOnError)
        {
            if (!typeReference.ContainsKey(className/*.ToLower()*/))
            {

                /*Type t = DiscoverType(className);
                if (t == null) */
                if (throwOnError) throw new Exception();
                return new KeyValuePair<Type, Dictionary<string, MethodInfo>>(null, null);
                /*lock (syncRoot)
                {
                    if (!typeReference.ContainsKey(className))
                    {
                        typeReference.Add(className, t);
                    }
                }*/
            }
            return typeReference[className];
        }
        private KeyValuePair<Type, Dictionary<string, MethodInfo>> GetTypeName(string path, out string typeName, out string functionName)
        {
            KeyValuePair<Type, Dictionary<string, MethodInfo>> typeRef = new KeyValuePair<Type, Dictionary<string, MethodInfo>>(null, null);
            typeName = functionName = string.Empty;
            foreach (string entity in path.Split('/'))
            {
                if (string.IsNullOrEmpty(typeName)) { if ((typeRef = GetTypeReference(entity, false)).Key != null) typeName = entity; }
                else { functionName = entity; break; }
            }
            return typeRef;
            //return typeName;
        }
        private object Call(object context, System.Reflection.MethodInfo method, NameValueCollection queryNameValue, HttpContext httpContext)
        {
            System.Reflection.ParameterInfo[] parameters = method.GetParameters();
            if (parameters.Length == 0) return method.Invoke(context, null);
            List<object> paramArgs = new List<object>();
            foreach (ParameterInfo pi in parameters)
            {
                if (pi.ParameterType == httpContext.GetType())
                {
                    paramArgs.Add(httpContext);
                    continue;
                }
                string qofParam = queryNameValue[pi.Name];
                if (qofParam == null) paramArgs.Add(null);
                else
                {
                    paramArgs.Add(TypeConversion.ConvertType(pi.Name, qofParam, pi.ParameterType));
                }
            }
            
            return method.Invoke(context, paramArgs.ToArray());//newParamList.ToArray());
        }
        public void Start()
        {
            VerifyState(HttpServerState.Stopped);

            State = HttpServerState.Starting;

            Log.Debug(String.Format("XmServer正在启动： {0}", EndPoint));

            // Start the listener.

            var listener = new TcpListener(EndPoint);

            try
            {
                listener.Start();

                EndPoint = (IPEndPoint)listener.LocalEndpoint;

                _listener = listener;

                ServerUtility = new HttpServerUtility();

                Log.Info(String.Format("XmServer已启动：{0}", EndPoint));
            }
            catch (Exception ex)
            {
                State = HttpServerState.Stopped;

                Log.Error("XmServer启动失败", ex);

                throw new XmHttpServerException("XmServer启动失败", ex);
            }

            State = HttpServerState.Started;

            BeginAcceptTcpClient();
        }
        /// <summary>
        /// 执行http请求url对应的方法
        /// </summary>
        /// <param name="verb">http请求方式（get、post等）</param>
        /// <param name="functionName">方法名</param>
        /// <param name="typeRef">方法名\参照对应关系</param>
        /// <param name="shouldSerializeXml">是否序列化为xml</param>
        /// <param name="queryNameValue">从请求数据转换过来的NameValueCollection（请求参数与值的对应）</param>
        /// <returns></returns>
        private object ServiceRequest(HttpVerb verb, string functionName, ref KeyValuePair<Type, Dictionary<string, MethodInfo>> typeRef, out string serializeType, NameValueCollection queryNameValue, HttpContext context)
        {
            serializeType = Text.type;
            object returnObject = null;
            System.Reflection.MethodInfo method = null;
            if (typeof(IRestFulService).IsAssignableFrom(typeRef.Key))
            {
                IRestFulService svcObject = (IRestFulService)typeRef.Key.GetConstructor(System.Type.EmptyTypes).Invoke(null);
                returnObject = ServiceRestFullCall(verb, functionName, returnObject, svcObject,context);
                serializeType = Xml.type;
            }
            else if (!string.IsNullOrEmpty(functionName) && typeRef.Value.ContainsKey(functionName))
            {
                method = typeRef.Value[functionName];

                if (((RestMethod)(method.GetCustomAttributes(typeof(RestMethod), false)[0])).Verb != verb)
                {

                    throw new InternalErrorException(503, "Method not supported");
                }
                returnObject = Call(typeRef.Key.GetConstructor(System.Type.EmptyTypes).Invoke(null), method, queryNameValue,context);

                bool isXml = method.GetCustomAttributes(typeof(Xml), false).Length > 0;
                if (isXml)
                {
                    serializeType = Xml.type;
                }
                bool isJson = method.GetCustomAttributes(typeof(Json), false).Length > 0;
                if (isJson)
                {
                    serializeType = Json.type;
                }
                
            }
            else
            {
                throw new InternalErrorException(503, "Method not supported");
            }
            return returnObject;
        }
        /// <summary>
        /// 若某个类继承自IRestFulService，则请求时通过该方法转发
        /// </summary>
        /// <param name="verb">http请求类型</param>
        /// <param name="functionName">方法名</param>
        /// <param name="returnObject">要向页面写入的数据的对象</param>
        /// <param name="svcObject">继承自IRestFulService的类</param>
        /// <returns></returns>
        private static object ServiceRestFullCall(HttpVerb verb, string functionName, object returnObject, IRestFulService svcObject, HttpContext context)
        {
            switch (verb)
            {
                case HttpVerb.Get:
                    returnObject = svcObject.GetCall(functionName);
                    break;
                case HttpVerb.Put:
                    returnObject = svcObject.PutCall(functionName);
                    break;
                case HttpVerb.Post:
                    returnObject = svcObject.PostCall(functionName);
                    break;
                case HttpVerb.Delete:
                    returnObject = svcObject.DeleteCall(functionName);
                    break;
                default:
                    throw new InternalErrorException(503, "Method not supported");

            }
            return returnObject;
        }
        public void Stop()
        {
            VerifyState(HttpServerState.Started);

            Log.Debug("XmServer正在关闭...");

            State = HttpServerState.Stopping;

            try
            {
                // Prevent any new connections.

                _listener.Stop();

                // Wait for all clients to complete.

                StopClients();
            }
            catch (Exception ex)
            {
                Log.Error("XmServer关闭失败", ex);

                throw new XmHttpServerException("XmServer关闭失败", ex);
            }
            finally
            {
                _listener = null;

                State = HttpServerState.Stopped;

                Log.Info("XmServer关闭成功！");
            }
        }

        private void StopClients()
        {
            var shutdownStarted = DateTime.Now;
            bool forceShutdown = false;

            // Clients that are waiting for new requests are closed.

            List<HttpClient> clients;

            lock (_syncLock)
            {
                clients = new List<HttpClient>(_clients.Keys);
            }

            foreach (var client in clients)
            {
                if (ClientDisconnected != null)
                    ClientDisconnected(this, new HttpClientEventArgs(client));
                client.RequestClose();
            }

            // First give all clients a chance to complete their running requests.

            while (true)
            {
                lock (_syncLock)
                {
                    if (_clients.Count == 0)
                        break;
                }

                var shutdownRunning = DateTime.Now - shutdownStarted;

                if (shutdownRunning >= ShutdownTimeout)
                {
                    forceShutdown = true;
                    break;
                }

                _clientsChangedEvent.WaitOne(ShutdownTimeout - shutdownRunning);
            }

            if (!forceShutdown)
                return;

            // If there are still clients running after the timeout, their
            // connections will be forcibly closed.

            lock (_syncLock)
            {
                clients = new List<HttpClient>(_clients.Keys);
            }

            foreach (var client in clients)
            {
                client.ForceClose();
            }

            // Wait for the registered clients to be cleared.

            while (true)
            {
                lock (_syncLock)
                {
                    if (_clients.Count == 0)
                        break;
                }

                _clientsChangedEvent.WaitOne();
            }
        }

        private void BeginAcceptTcpClient()
        {
            if (_state != HttpServerState.Started)
                return;

            _listener.BeginAcceptTcpClient(AcceptTcpClientCallback, null);
        }

        private void AcceptTcpClientCallback(IAsyncResult asyncResult)
        {
            if (_state != HttpServerState.Started)
                return;

            try
            {
                var listener = _listener; // Prevent race condition.

                if (listener == null)
                    return;

                var tcpClient = listener.EndAcceptTcpClient(asyncResult);

                var client = new HttpClient(this, tcpClient);

                RegisterClient(client);

                client.BeginRequest();

                BeginAcceptTcpClient();
            }
            catch (ObjectDisposedException)
            {
                // EndAcceptTcpClient will throw a ObjectDisposedException
                // when we're shutting down. This can safely be ignored.
            }
            catch (Exception ex)
            {
                Log.Info("Failed to accept TCP client", ex);
            }
        }

        private void RegisterClient(HttpClient client)
        {
            if (client == null)
                throw new ArgumentNullException("client");

            lock (_syncLock)
            {
                _clients.Add(client, true);

                _clientsChangedEvent.Set();

                if (ClientConnected != null)
                    ClientConnected(this, new HttpClientEventArgs(client));

            }
        }

        internal void UnregisterClient(HttpClient client)
        {
            if (client == null)
                throw new ArgumentNullException("client");
            

            lock (_syncLock)
            {
                
                Debug.Assert(_clients.ContainsKey(client));
                
                _clients.Remove(client);

                _clientsChangedEvent.Set();

                
            }
            
        }

        private void VerifyState(HttpServerState state)
        {
            if (_disposed)
                throw new ObjectDisposedException(GetType().Name);
            if (_state != state)
                throw new InvalidOperationException(String.Format("Expected server to be in the '{0}' state", state));
        }

        public void Dispose()
        {
            if (!_disposed)
            {
                if (_state == HttpServerState.Started)
                    Stop();

                if (_clientsChangedEvent != null)
                {
                    ((IDisposable)_clientsChangedEvent).Dispose();
                    _clientsChangedEvent = null;
                }

                _disposed = true;
            }
        }

        internal void RaiseRequest(HttpContext context)
        {
            if (context == null)
                throw new ArgumentNullException("context");
            OnRequestReceived(new HttpRequestEventArgs(context));

            //base auth
            if (this.RequireAuthentication && this.Authenticator.Authenticate(context.Request.Username, context.Request.Password) == false)
            {
                context.Response.Headers.Add("WWW-Authenticate", "Basic realm=\"" + this.AuthenticateRealm + "\"");
                context.Response.StatusCode = 401;
                context.Response.StatusDescription = StatusCodes.GetDescription("401");

                string message = context.Response.StatusCode + " " + context.Response.StatusDescription;
                StringBuilder sb = new StringBuilder();
                sb.AppendLine("<html><head><title>" + message + "</title></head>");
                sb.AppendLine("<body><h2>" + message + "</h2>");
                sb.AppendLine("<hr>" + this.ServerBanner);
                sb.AppendLine("</body></html>");

                using (var writer = new StreamWriter(context.Response.OutputStream))
                {
                    writer.Write(sb.ToString());
                }
                return;
            }

            string absolutePath = context.Request.Url.AbsolutePath;

            //index page
            if ("/".Equals(absolutePath) || "".Equals(absolutePath))
            {
                using (var writer = new StreamWriter(context.Response.OutputStream))
                {
                    writer.Write(Tools.ServeHelp(typeReference,this,this.EndPoint.Address.ToString(),this.EndPoint.Port).ToString());
                }
                return;
            }


            //==============rest===begin===========
            string typeName, functionName;
            KeyValuePair<Type, Dictionary<string, MethodInfo>> typeRef = GetTypeName(absolutePath, out typeName, out functionName);
            if (typeRef.Key != null && typeRef.Value.ContainsKey(functionName))//!string.IsNullOrEmpty(typeName))
            {
                HttpVerb verb = (HttpVerb)Enum.Parse(typeof(HttpVerb), context.Request.HttpMethod, true); //HttpVerb verb = (HttpVerb)Enum.Parse(typeof(HttpVerb), "POST", true);
                object returnObject = null;
                string serializeType;
                returnObject = ServiceRequest(verb, functionName, ref typeRef, out serializeType, context.Request.Params, context);
                string returnString = "";
                switch (serializeType)
                {
                    case Text.type:
                        context.Response.ContentType = "text/html";
                        returnString = (returnObject == null ? "" : returnObject.ToString());
                        break;
                    case Xml.type:
                        context.Response.ContentType = "text/xml";
                        returnString = SerializeUtil.Object2Xml(returnObject);
                        break;
                    case Json.type:
                        context.Response.ContentType = "text/html";
                        returnString = SerializeUtil.Object2Json(returnObject);
                        break;
                }
                using (var writer = new StreamWriter(context.Response.OutputStream))
                {
                    writer.Write(returnString);
                }
                return;
            } //==============rest===end===========
            else
            {
                //==============file===begin===========
                string basePath = null;//包含在设置的directory内的文件路径
                string baseKey = null;//包含在设置的directory内的url路径
                foreach (string key in directorys.Keys)
                {
                    if (absolutePath.IndexOf(key) == 0)
                    {
                        basePath = directorys.Get(key);
                        if (!basePath.EndsWith("\\"))
                        {
                            basePath = basePath + "\\";
                        }
                        baseKey = key;
                        break;
                    }
                }
                if (basePath == null || baseKey == null)
                {
                    return;
                }
                
                //byte[] b = getFileContent(absolutePath);
                byte[] b = getFileContent(basePath + absolutePath.Substring(baseKey.Length), absolutePath,context);
                if (b != null)
                {
                    using (var writer = context.Response.OutputStream)
                    {
                        writer.Write(b, 0, b.Length);
                    }
                    b = null;
                }
                //==============file===end===========
            }
        }
        private byte[] getFileContent(string filePath,string url,HttpContext context)
        {
            byte[] buffer = null;
            try
            {
                string LocalFileName = filePath.Replace('/', '\\');
                DirectoryInfo directoryInfo = new DirectoryInfo(LocalFileName);
                if (directoryInfo.Exists)
                {
                    context.Response.ContentType = "text/html";
                    string responseString = getFilesList(LocalFileName, url);
                    buffer = Encoding.UTF8.GetBytes(responseString);
                    return buffer;
                }
                FileInfo fileInfo = new FileInfo(LocalFileName);
                if (fileInfo.Exists)
                {
                    context.Response.ContentType = "application/octet-stream";
                    using (FileStream WebFile = new FileStream(LocalFileName, FileMode.Open))
                    {
                        buffer = new byte[WebFile.Length];
                        WebFile.Read(buffer, 0, buffer.Length);
                        WebFile.Close();
                    }
                }

            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                //string responseString = "ERROR : " + e.Message;
                //buffer = Encoding.UTF8.GetBytes(responseString);
            }
            return buffer;
        }

        private static string staticPath = Application.StartupPath ;
        private byte[] getFileContent(string url)
        {
            byte[] buffer = null;
            try
            {
                string LocalFileName = "";
                url = System.Web.HttpUtility.UrlDecode(url);
                LocalFileName = staticPath + url.Replace('/', '\\');
                DirectoryInfo directoryInfo = new DirectoryInfo(LocalFileName);
                if (directoryInfo.Exists)
                {
                    string responseString = getFilesList(LocalFileName,url);
                    buffer = Encoding.UTF8.GetBytes(responseString);
                    return buffer;
                }
                FileInfo fileInfo = new FileInfo(LocalFileName);
                if (fileInfo.Exists)
                {
                    using (FileStream WebFile = new FileStream(LocalFileName, FileMode.Open))
                    {
                        buffer = new byte[WebFile.Length];
                        WebFile.Read(buffer, 0, buffer.Length);
                        WebFile.Close();
                    }
                }
                
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                //string responseString = "ERROR : " + e.Message;
                //buffer = Encoding.UTF8.GetBytes(responseString);
            }
            return buffer;
        }
        private string getFilesList(string path,string url)
        {
            
            string result = "<html><head><title>目录与文件列表</title></head><body>";
            result += "<font size=\"3\" face=\"verdana\">";

            string[] directorys = Directory.GetDirectories(path);
            foreach (string s in directorys)
            {
                string[] splited = s.Split('\\');
                string name = splited[splited.Length - 1];
                string href = url.EndsWith("/") ? name : url + "\\" + name;
                result += "<a href=\"" + href + "/" + "\">[" + name + "]</a></br>";
            }

            string[] files = Directory.GetFiles(path);
            foreach (string s in files)
            {
                string[] splited = s.Split('\\');
                string name = splited[splited.Length - 1];
                string href = url.EndsWith("/") ? name : url + "\\" + name;
                result += "<a href=\"" +href + "\">" + name + "</a></br>";
            }
            result += "</font></body></html>";
            return result;
        }

        internal bool RaiseUnhandledException(HttpContext context, Exception exception)
        {
            if (context == null)
                throw new ArgumentNullException("context");

            var e = new HttpExceptionEventArgs(context, exception);

            OnUnhandledException(e);

            return e.Handled;
        }
    }
}
